/**
 * Copyright (C) 2010 Sergey Grigoriev <s.a.grigoriev@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.gmf.test

import groovy.xml.StreamingMarkupBuilder

import org.apache.commons.io.FileUtils
import org.apache.commons.lang.StringUtils

import org.apache.maven.plugin.MojoFailureException
import org.apache.maven.project.MavenProject
import org.apache.mina.core.service.IoAcceptor
import org.apache.mina.core.service.IoHandler
import org.apache.mina.core.session.IdleStatus
import org.apache.mina.filter.codec.ProtocolCodecFilter
import org.apache.mina.filter.logging.LoggingFilter
import org.apache.mina.transport.socket.nio.NioSocketAcceptor
import org.apache.log4j.Logger
import com.google.inject.Inject
import net.gmf.dsl.FlexProject
import net.gmf.core.FlexProjectUtils

/**
 * Executes tests
 */
class ExecuteTestsCommand {
/**
 * The port number for CIListener.
 */
  private int getPortNumber() { flexProject.rootNode.testRunner.port}


  private String getFlashPlayer() { flexProject.rootNode.testRunner.flashPlayer }

  @Inject
  MavenProject project

  private File getReportsDirectory() {
    return new File([project.build.directory, "surefire-reports"].join(File.separator))
  }


  @Inject
  private IoHandler flexUnitConnector

  /** Set this to 'true' to skip running tests
   *
   */
  boolean skipTests

  /**
   * Set this to true to ignore a failure during testing
   *
   */
  boolean testFailureIgnore

  @Inject
  private FlexProject flexProject


  private final Logger l4j = Logger.getLogger(this.getClass())


  def run() {

    if (skipTests) {
      return
    }

    String url = flexProject.rootNode.testRunner.target

    if (StringUtils.isBlank(url)) {
      if (!new File(project.build.testSourceDirectory).exists()) {
        return
      }
      url = new FlexProjectUtils(project).testCompilationTarget
    }

    FileUtils.forceMkdir(reportsDirectory)

    if (StringUtils.isBlank(flashPlayer)) {
      throw new IllegalStateException("Parameter 'flashPlayer' must be set to run unit tests")
    }

    def ioAcceptor = startServer()
    Process p
    final def commadLine = [flashPlayer, url]
    l4j.info(commadLine.join(' '))
    try {
      p = commadLine.execute()
      flexUnitConnector.waitForAllTestsToComplete()
    } finally {
      ioAcceptor.unbind()
    }
    createXmlForSureFire(flexUnitConnector.content.toString())
    p.destroy()
  }

  def private createXmlForSureFire(String source) {

    def dom = parseUngroupedTests(source)
    def fixtures = groupTestsByFixtures(dom)
    createReportForEveryFixture(fixtures)

  }

  def private createReportForEveryFixture(LinkedHashMap fixtures) {
    boolean testsPassed = true
    fixtures.each { fixture, testcases ->
      final def fileName = "TEST-$fixture" + ".xml"
      File file = new File([reportsDirectory.path, fileName].join(File.separator))
      StreamingMarkupBuilder streamingMarkupBuilder = new StreamingMarkupBuilder()

      int errors = 0
      int failures = 0
      testcases.each { testCaseInfo ->
        errors += testCaseInfo.error ? 1 : 0
        failures += testCaseInfo.failed ? 1 : 0
      }
      if (errors > 0 || failures > 0) {
        testsPassed = false
      }

      def xml = streamingMarkupBuilder.bind({
        mkp.xmlDeclaration()
        testsuite(name: fixture, tests: testcases.size(), errors: errors, failures: failures) {
          testcases.each { mkp.yieldUnescaped(it.xml) }
        }
      })
      file.write(xml.toString())
    }


    final def msg = "Some tests failed, see reports in $reportsDirectory"
    if (testFailureIgnore) {
      l4j.warn(msg)
    } else if (!testsPassed) {
      throw new MojoFailureException(msg)
    }
  }

  private def parseUngroupedTests(String source) {
    // Add header and root element to the data to be able to parse xml
    StreamingMarkupBuilder streamingMarkupBuilder = new StreamingMarkupBuilder()
    XmlParser parser = new XmlParser()
    def dom = parser.parseText(streamingMarkupBuilder.bind({
      mkp.xmlDeclaration()
      testsuite {
        mkp.yieldUnescaped(source)
      }
    }).toString())
    return dom
  }

  def private groupTestsByFixtures(dom) {
    def textFixtures = [:]
    dom.testcase.each {
      final def key = it['@classname'].replace("::", ".")
      if (!textFixtures[key]) {
        textFixtures[key] = []
      }
      TestCaseInfo testCaseInfo = new TestCaseInfo()
      def tests = textFixtures[key]
      StringWriter sw = new StringWriter()
      def XmlNodePrinter printer = new XmlNodePrinter(new PrintWriter(sw, true))
      it["@classname"] = key
      printer.print(it)
      testCaseInfo.xml = sw.toString()
      if (it.failure) {
        testCaseInfo.failed = true
      }
      if (it.error) {
        testCaseInfo.error = true
      }

      tests << testCaseInfo
    }
    return textFixtures
  }



  def private startServer() {
    IoAcceptor acceptor = new NioSocketAcceptor();

    acceptor.getFilterChain().addLast("logger", new LoggingFilter());
    acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new XmlSocketCodecFactory()));

    acceptor.setHandler(flexUnitConnector);
    acceptor.getSessionConfig().setReadBufferSize(2048);
    acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 2);
    acceptor.bind(new InetSocketAddress(portNumber));
    return acceptor;
  }
}
